package com.dge.common.extentions

import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.RippleDrawable
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.*
import android.view.View
import androidx.core.text.isDigitsOnly
import androidx.fragment.app.Fragment
import com.blankj.utilcode.util.TimeUtils
import com.google.gson.ExclusionStrategy
import com.google.gson.FieldAttributes
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import com.dge.common.LibApp
import com.dge.common.utils.SizeConvertUtils
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.Serializable
import java.math.BigDecimal
import java.util.*
import java.util.regex.Pattern

/**
 * Description:  通用扩展
 * Create by dance, at 2018/12/5
 */
/**
 * 获取该dp值所对应的px值
 */
val Float.dp
    get() = SizeConvertUtils.dp2px(this).toFloat()

/**
 * 获取该dp值所对应的px值
 */
val Int.dp
    get() = SizeConvertUtils.dp2px(this.toFloat())

/**
 * 获取该sp值所对应的px值
 */
val Float.sp
    get() = SizeConvertUtils.sp2px(this).toFloat()

/**
 * 获取该sp值所对应的px值
 */
val Int.sp
    get() = SizeConvertUtils.sp2px(this.toFloat())

/** 动态创建Drawable **/
fun Context.createDrawable(
    color: Int = Color.TRANSPARENT,
    radius: Float = 0f,
    strokeColor: Int = Color.TRANSPARENT,
    strokeWidth: Int = 0,
    enableRipple: Boolean = true,
    rippleColor: Int = Color.parseColor("#88999999"),
    gradientStartColor: Int = 0,
    gradientEndColor: Int = 0,
    gradientOrientation: GradientDrawable.Orientation = GradientDrawable.Orientation.LEFT_RIGHT
): Drawable {
    val content = GradientDrawable().apply {
        cornerRadius = radius
        setStroke(strokeWidth, strokeColor)
        gradientType = GradientDrawable.LINEAR_GRADIENT
        if (gradientStartColor != 0 || gradientEndColor != 0) {
            orientation = gradientOrientation
            colors = intArrayOf(gradientStartColor, gradientEndColor)
        } else {
            setColor(color)
        }
    }
    if (Build.VERSION.SDK_INT >= 21 && enableRipple) {
        return RippleDrawable(ColorStateList.valueOf(rippleColor), content, null)
    }
    return content
}

/** 动态创建Drawable **/
fun Context.createDrawable(
    color: Int = Color.TRANSPARENT,
    topLeftRadius: Float = 0f,
    topRightRadius: Float = 0f,
    bottomLeftRadius: Float = 0f,
    bottomRightRadius: Float = 0f,
    strokeColor: Int = Color.TRANSPARENT,
    strokeWidth: Int = 0,
    enableRipple: Boolean = true,
    rippleColor: Int = Color.parseColor("#88999999"),
    gradientStartColor: Int = 0,
    gradientEndColor: Int = 0,
    gradientOrientation: GradientDrawable.Orientation = GradientDrawable.Orientation.LEFT_RIGHT
): Drawable {
    val content = GradientDrawable().apply {
        cornerRadii = floatArrayOf(
            topLeftRadius,
            topLeftRadius,
            topRightRadius,
            topRightRadius,
            bottomLeftRadius,
            bottomLeftRadius,
            bottomRightRadius,
            bottomRightRadius
        )
        setStroke(strokeWidth, strokeColor)
        gradientType = GradientDrawable.LINEAR_GRADIENT
        if (gradientStartColor != 0 || gradientEndColor != 0) {
            orientation = gradientOrientation
            colors = intArrayOf(gradientStartColor, gradientEndColor)
        } else {
            setColor(color)
        }
    }
    if (Build.VERSION.SDK_INT >= 21 && enableRipple) {
        return RippleDrawable(ColorStateList.valueOf(rippleColor), content, null)
    }
    return content
}

fun Fragment.createDrawable(
    color: Int = Color.TRANSPARENT,
    radius: Float = 0f,
    strokeColor: Int = Color.TRANSPARENT,
    strokeWidth: Int = 0,
    enableRipple: Boolean = true,
    rippleColor: Int = Color.parseColor("#88999999"),
    gradientStartColor: Int = 0,
    gradientEndColor: Int = 0,
    gradientOrientation: GradientDrawable.Orientation = GradientDrawable.Orientation.LEFT_RIGHT
): Drawable {
    return context!!.createDrawable(
        color,
        radius,
        strokeColor,
        strokeWidth,
        enableRipple,
        rippleColor,
        gradientStartColor = gradientStartColor,
        gradientEndColor = gradientEndColor,
        gradientOrientation = gradientOrientation
    )
}

fun Fragment.createDrawable(
    color: Int = Color.TRANSPARENT,
    topLeftRadius: Float = 0f,
    topRightRadius: Float = 0f,
    bottomLeftRadius: Float = 0f,
    bottomRightRadius: Float = 0f,
    strokeColor: Int = Color.TRANSPARENT,
    strokeWidth: Int = 0,
    enableRipple: Boolean = true,
    rippleColor: Int = Color.parseColor("#88999999"),
    gradientStartColor: Int = 0,
    gradientEndColor: Int = 0,
    gradientOrientation: GradientDrawable.Orientation = GradientDrawable.Orientation.LEFT_RIGHT
): Drawable {
    return context!!.createDrawable(
        color,
        topLeftRadius,
        topRightRadius,
        bottomLeftRadius,
        bottomRightRadius,
        strokeColor,
        strokeWidth,
        enableRipple,
        rippleColor,
        gradientStartColor = gradientStartColor,
        gradientEndColor = gradientEndColor,
        gradientOrientation = gradientOrientation
    )
}

fun View.createDrawable(
    color: Int = Color.TRANSPARENT,
    radius: Float = 0f,
    strokeColor: Int = Color.TRANSPARENT,
    strokeWidth: Int = 0,
    enableRipple: Boolean = true,
    rippleColor: Int = Color.parseColor("#88999999"),
    gradientStartColor: Int = 0,
    gradientEndColor: Int = 0,
    gradientOrientation: GradientDrawable.Orientation = GradientDrawable.Orientation.LEFT_RIGHT
): Drawable {
    return context!!.createDrawable(
        color,
        radius,
        strokeColor,
        strokeWidth,
        enableRipple,
        rippleColor,
        gradientStartColor = gradientStartColor,
        gradientEndColor = gradientEndColor,
        gradientOrientation = gradientOrientation
    )
}

fun View.createDrawable(
    color: Int = Color.TRANSPARENT,
    topLeftRadius: Float = 0f,
    topRightRadius: Float = 0f,
    bottomLeftRadius: Float = 0f,
    bottomRightRadius: Float = 0f,
    strokeColor: Int = Color.TRANSPARENT,
    strokeWidth: Int = 0,
    enableRipple: Boolean = true,
    rippleColor: Int = Color.parseColor("#88999999"),
    gradientStartColor: Int = 0,
    gradientEndColor: Int = 0,
    gradientOrientation: GradientDrawable.Orientation = GradientDrawable.Orientation.LEFT_RIGHT
): Drawable {
    return context!!.createDrawable(
        color,
        topLeftRadius,
        topRightRadius,
        bottomLeftRadius,
        bottomRightRadius,
        strokeColor,
        strokeWidth,
        enableRipple,
        rippleColor,
        gradientStartColor = gradientStartColor,
        gradientEndColor = gradientEndColor,
        gradientOrientation = gradientOrientation
    )
}

/** json相关 **/
fun Any.toJson(
    dateFormat: String = "yyyy-MM-dd HH:mm:ss",
    lenient: Boolean = false,
    excludeFields: List<String>? = null
) =
    GsonBuilder().setDateFormat(dateFormat)
        .apply {
            if (lenient) setLenient()
            if (!excludeFields.isNullOrEmpty()) {
                setExclusionStrategies(object : ExclusionStrategy {
                    override fun shouldSkipField(f: FieldAttributes?): Boolean {
                        return f != null && excludeFields.contains(f.name)
                    }

                    override fun shouldSkipClass(clazz: Class<*>?) = false
                })
            }
        }
        .create().toJson(this)

inline fun <reified T> String.toBean(
    dateFormat: String = "yyyy-MM-dd HH:mm:ss",
    lenient: Boolean = false
) =
    GsonBuilder().setDateFormat(dateFormat)
        .apply {
            if (lenient) setLenient()
        }.create()
        .fromJson<T>(this, object : TypeToken<T>() {}.type)

/**
 * 数组转bundle
 */
fun Array<out Pair<String, Any?>>.toBundle(): Bundle {
    return Bundle().apply {
        forEach { it ->
            val value = it.second
            when (value) {
                null -> putSerializable(it.first, null as Serializable?)
                is Int -> putInt(it.first, value)
                is Long -> putLong(it.first, value)
                is String -> putString(it.first, value)
                is CharSequence -> putCharSequence(it.first, value)
                is Float -> putFloat(it.first, value)
                is Double -> putDouble(it.first, value)
                is Char -> putChar(it.first, value)
                is Short -> putShort(it.first, value)
                is Boolean -> putBoolean(it.first, value)
                is IntArray -> putIntArray(it.first, value)
                is LongArray -> putLongArray(it.first, value)
                is FloatArray -> putFloatArray(it.first, value)
                is DoubleArray -> putDoubleArray(it.first, value)
                is CharArray -> putCharArray(it.first, value)
                is ShortArray -> putShortArray(it.first, value)
                is BooleanArray -> putBooleanArray(it.first, value)
                is Array<*> -> when {
                    value.isArrayOf<CharSequence>() -> putCharSequenceArray(
                        it.first,
                        value as Array<CharSequence>
                    )

                    value.isArrayOf<String>() -> putStringArray(it.first, value as Array<String>)
                    value.isArrayOf<Parcelable>() -> putParcelableArray(
                        it.first,
                        value as Array<Parcelable>
                    )
                }

                is Serializable -> putSerializable(it.first, value)
                is Parcelable -> putParcelable(it.first, value)
            }
        }
    }
}

fun Any.runOnUIThread(action: () -> Unit) {
    Handler(Looper.getMainLooper()).post { action() }
}

/**
 * 将Bitmap保存到相册
 */
fun Bitmap.saveToAlbum(
    format: Bitmap.CompressFormat = Bitmap.CompressFormat.PNG,
    quality: Int = 100,
    filename: String = "",
    callback: ((path: String?, uri: Uri?) -> Unit)? = null
) {
    GlobalScope.launch {
        try {
            //1. create path
            val dirPath =
                Environment.getExternalStorageDirectory().absolutePath + "/" + Environment.DIRECTORY_PICTURES
            val dirFile = File(dirPath)
            if (!dirFile.exists()) dirFile.mkdirs()
            val ext = when (format) {
                Bitmap.CompressFormat.PNG -> ".png"
                Bitmap.CompressFormat.JPEG -> ".jpg"
                Bitmap.CompressFormat.WEBP -> ".webp"
                else -> ".png"
            }
//            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
//                if (format == Bitmap.CompressFormat.WEBP_LOSSY || format == Bitmap.CompressFormat.WEBP_LOSSLESS) {
//                    ext = ".webp"
//                }
//            }
            val target = File(
                dirPath,
                (if (filename.isEmpty()) System.currentTimeMillis().toString() else filename) + ext
            )
            if (target.exists()) target.delete()
            target.createNewFile()
            //2. save
            compress(format, quality, FileOutputStream(target))
            //3. notify
            MediaScannerConnection.scanFile(
                LibApp.context, arrayOf(target.absolutePath),
                arrayOf("image/$ext")
            ) { path, uri ->
                runOnUIThread {
                    callback?.invoke(path, uri)
                }
            }
        } catch (e: IOException) {
            e.printStackTrace()
            runOnUIThread { callback?.invoke(null, null) }
        }
    }
}

//一天只做一次
fun Any.doOnceInDay(
    actionName: String = "",
    action: () -> Unit,
    whenHasDone: (() -> Unit)? = null
) {
    val key = "once_in_day_last_check_${actionName}"
    val today = Date()
    val todayFormat = TimeUtils.date2String(today, "yyyy-MM-dd")
    val last = mmkv().getString(key, "")
    if (last != null && last.isNotEmpty() && last == todayFormat) {
        //说明执行过
        whenHasDone?.invoke()
        return
    }
    mmkv().putString(key, todayFormat)
    action()
}

//只执行一次的行为
fun Any.doOnlyOnce(actionName: String = "", action: () -> Unit, whenHasDone: (() -> Unit)? = null) {
    val key = "has_done_${actionName}"
    val hasDone = mmkv().getBoolean(key, false)
    if (hasDone) {
        //说明执行过
        whenHasDone?.invoke()
        return
    }
    mmkv().putBoolean(key, true)
    action()
}

//500毫秒内只做一次
val _innerHandler = Handler(Looper.getMainLooper())
val _actionCache = arrayListOf<String>()

/**
 * 事件节流
 * @param actionName 事件的名字
 * @param time 事件的节流时间
 * @param action 事件
 */
fun Any.doOnceIn(actionName: String, time: Long = 500, action: () -> Unit) {
    if (_actionCache.contains(actionName)) return
    _actionCache.add(actionName)
    action() //执行行为
    _innerHandler.postDelayed({
        if (_actionCache.contains(actionName)) _actionCache.remove(actionName)
    }, time)
}

fun BigDecimal?.roundUp(newScale: Int): String {
    val scale = if (newScale > 0) newScale else 0
    return this?.setScale(scale, BigDecimal.ROUND_HALF_UP)?.toString()
        ?: BigDecimal.ZERO.setScale(scale, BigDecimal.ROUND_HALF_UP).toString()
}

fun Double?.roundUp(newScale: Int): String {
    val scale = if (newScale > 0) newScale else 0
    return if (this != null)
        BigDecimal.valueOf(this).setScale(scale, BigDecimal.ROUND_HALF_UP).toString()
    else
        BigDecimal.ZERO.roundUp(scale)
}

fun Double.subtract(value: Double, scale: Int = 2, round: Int = BigDecimal.ROUND_HALF_UP): Double {
    return BigDecimal(this - value).setScale(scale, round).toDouble()
}

fun Double.plusValue(value: Double, scale: Int = 2, round: Int = BigDecimal.ROUND_HALF_UP): Double {
    return BigDecimal(this + value).setScale(scale, round).toDouble()
}

/**
 * 比较double的大小
 */
fun Double.roundCompare(value: Double, scale: Int = 2, round: Int = BigDecimal.ROUND_HALF_UP): Int {
    val value1 = BigDecimal(this).setScale(scale, round)
    val value2 = BigDecimal(value).setScale(scale, round)
    return value1.compareTo(value2)
}

fun BigDecimal?.roundDown(newScale: Int): String {
    val scale = if (newScale > 0) newScale else 0
    return this?.setScale(scale, BigDecimal.ROUND_HALF_DOWN)?.toString()
        ?: BigDecimal.ZERO.setScale(scale, BigDecimal.ROUND_HALF_DOWN).toString()
}

fun Double?.roundDown(newScale: Int): String {
    val scale = if (newScale > 0) newScale else 0
    return if (this != null)
        BigDecimal.valueOf(this).setScale(scale, BigDecimal.ROUND_HALF_DOWN).toString()
    else
        BigDecimal.ZERO.roundDown(scale)
}

//全员TAG类
val Any.TAG: String
    get() {
        return if (!javaClass.isAnonymousClass) {
            val name = javaClass.simpleName
            // 日志 TAG 长度限制已经在 Android 8.0 被移除
            if (name.length <= 23 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) name else name.substring(
                0,
                23
            )
        } else {
            val name = javaClass.name
            // 日志 TAG 长度限制已经在 Android 8.0 被移除
            if (name.length <= 23 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) name else name.substring(
                name.length - 23,
                name.length
            )
        }
    }

/**
 * 判断小数位是否为0
 */
fun Double.isActuallyInteger(): Boolean {
    val int = this.toInt()
    val double = int.toDouble()
    return this == double
}

fun String?.getIntValue(default: Int = 0): Int {
    val longValue = getLongValue(default.toLong())
    val intValue = if (longValue >= Int.MAX_VALUE) {
        Int.MAX_VALUE
    } else if (longValue <= Int.MIN_VALUE) {
        Int.MIN_VALUE
    } else {
        longValue.toInt()
    }
    return intValue
}

fun String?.getLongValue(default: Long = 0): Long {
    return if (this != null && this.isValid() && (this.replace(".", "").replace("-", "")
            .isDigitsOnly())
    ) (if (this.contains(".")) this.toDouble().toLong() else this.toLong()) else default
}

fun String?.getDoubleValue(default: String = "0.0"): Double {
    return this?.toDoubleOrIntStr(default)?.toDouble() ?: 0.0
}

/**
 * 将一个str转换成一个int或者double的字符串，如果不是int或double，则返回默认值,
 * 如果小数位后都是0，则转换成一个int的string字符串
 * @param default 默认值
 */
fun String?.toDoubleOrIntStr(default: String = "0"): String {
    if (this != null && this.isValid()) {
        if (this.contains(".")) {
            return if (this.replace(".", "").isDigitsOnly()) {
                val double = this.toDouble()
                if (double.isActuallyInteger()) {
                    double.toInt().toString()
                } else {
                    double.toString()
                }
            } else {
                default
            }
        } else {
            return if (this.isDigitsOnly()) {
                this.toString()
            } else {
                default
            }
        }
    } else {
        return default
    }
}

fun Double.toDoubleOrIntStr(default: String = "0"): String {
    return this.toString().toDoubleOrIntStr(default)
}

/**
 * 提取出字符串中的double
 */
fun String.findFloatFromString(): Double {
    val list: MutableList<Double> = ArrayList()
    // 声明正则，匹配数字（1个或多个）.数字（1个或多个）
    val regex = if (this.contains(".")) "\\d+\\.+\\d+" else "\\d+"
    // Pattern的构造方法是私有的，不可以直接创建，通过静态方法compile创建Pattern对象，查看源代码发现compile直接调用了Pattern构造函数。
    val pattern = Pattern.compile(regex)
    // 返回一个Matcher对象。Matcher类的构造方法也是私有的，不能随意创建，只能通过Pattern.matcher(CharSequence input)方法得到该类的实例。
    val matcher = pattern.matcher(this)
    // 对目标字符串进行正则匹配，通过while可以多次执行find方法，获取多次的匹配结果，代码编写方式类似于iterator.next()。
    while (matcher.find()) {
        // group() 返回匹配到的字符串，结合find函数使用。
        val group: String = matcher.group()
        list.add(group.toDouble())
    }
    return if (list.isNotEmpty()) {
        list[list.size - 1]
    } else {
        0.0
    }
}